/*
 * INIParser.cpp
 *
 *  Created on: 06.09.2012.
 *      Author: Aco
 */

/*
 * INIParser.h
 *
 *  Created on: Apr 29, 2011
 *      Author: burek
 */

#include "INIParser.h"

#include <cstdio>
#include <cstdlib>
#include <iostream>

#include "SMap.h"
#include "KeyValuePair.h"
#include "../log/Log.h"
#include "../stringutil.h"

#include "../../Engine.h"

INIGroup::INIGroup()
{
	title = NULL;
	vals.setDeleteOnExit(true);
}

INIGroup::~INIGroup()
{
	if(title) free(title);
	vals.clear(true);
}

SMap<KeyValuePair> & INIGroup::getVals() { return vals; }

void INIGroup::setTitle(const char * k)
{
	if(k == NULL) return;

	unsigned int nl = strlen(k);
	if(title == NULL)
	{
		title = (char*)malloc(nl + 1);
	}
	else if(strlen(title) < nl)
	{
		char * newPtr = (char*)realloc(title, nl + 1);
		if(newPtr == NULL)
		{
			return;
		}
		title = newPtr;
	}
	strcpy(title, k);
}

const char * INIGroup::getTitle() const
{
	return title;
}

int INIGroup::getSize() const
{
	return vals.getCount();
}

void INIGroup::putKVP(KeyValuePair * kvp)
{
	if(kvp == NULL) return;
	if(kvp->key == NULL) return;

	vals.put(kvp->key, kvp);
}

KeyValuePair * INIGroup::getKey(const char * key)
{
	return vals.get(key);
}

const char * INIGroup::getValue(const char * key)
{
	KeyValuePair * kvp = vals.get(key);
	if(kvp)
		return kvp->value;
	else
		return NULL;
}

KeyValuePair * INIGroup::touchKey(const char * key)
{
	KeyValuePair * kvp = vals.get(key);
	if(kvp)
	{
		return kvp;
	}
	else
	{
		kvp = new KeyValuePair(key, "");
		putKVP(kvp);
	}

	return kvp;
}

void INIGroup::setValue(const char * key, const char * value)
{
	KeyValuePair * kvp = touchKey(key);
	kvp->setValue(value);
}


INIParser::INIParser()
{
	lastFileName[0] = '\0';
	saveOnExit = false;
	//groups.setDeleteOnExit(true);
	//groups.setDeleteOnRemove(true);
}

unsigned int INIParser::calcLength()
{
	unsigned int len = 0;

	for(int i = 0; i < groups.getCount(); ++i)
	{
		INIGroup * g = groups.getNthValue(i);
		if(g == NULL) continue;

		len += 4 + strlen(g->getTitle());
		for(int n = 0; n < g->getVals().getCount(); ++n)
		{
			len += 3 + strlen(g->getVals().getNthKey(n)) + strlen(g->getVals().getNthValue(n)->value);
		}
		len += 2;
	}
	len++;

	return len;
}

int INIParser::getGroupCount() const
{
	return groups.getCount();
}

INIGroup * INIParser::getGroupAt(int i)
{
	return groups.getNthValue(i);
}

ByteStream * INIParser::saveToBuffer(ByteStream * bs)
{
	unsigned int len = calcLength();

	if(bs == NULL)
	{
		bs = new ByteStream(len);
	}
	else
	{
		if(bs->bufferSize < len)
		{
			bs->data = (unsigned char *)realloc(bs->data, len);
			bs->bufferSize = sizeof(bs->data);
		}
	}

	for(int i = 0; i < groups.getCount(); ++i)
	{
		INIGroup * g = groups.getNthValue(i);
		if(g == NULL) continue;

		if(!bs->writeSTRINGn("[", 1)) { delete bs; return NULL; }
		if(!bs->writeSTRINGn(g->getTitle(), 1024)) { delete bs; return NULL; }
		if(!bs->writeSTRINGn("]\n", 3)) { delete bs; return NULL; }

		for(int n = 0; n < g->getVals().getCount(); ++n)
		{
			if(!bs->writeSTRINGn(g->getVals().getNthKey(n), 1024)) { delete bs; return NULL; }
			if(!bs->writeSTRINGn("=", 2)) { delete bs; return NULL; }
			if(!bs->writeSTRINGn(g->getVals().getNthValue(n)->value, 1024)) { delete bs; return NULL; }
			if(!bs->writeSTRINGn("\n", 2)) { delete bs; return NULL; }
		}

		if(!bs->writeSTRINGn("\n", 2)) { delete bs; return NULL; }
	}

	return bs;
}

bool INIParser::saveToFile(const char * fileName)
{
	ByteStream * bs = saveToBuffer(NULL);
	if(bs == NULL) return false;
	if(bs->writePlain(fileName))
	{
		strcpy(lastFileName, fileName);
		delete bs;
		return true;
	}
	else
	{
		delete bs;
		return false;
	}
}

INIParser::~INIParser()
{
	if(saveOnExit && strlen(lastFileName) > 0)
		saveToFile(lastFileName);
	groups.clear(true);
}

void INIParser::setSaveOnExit(bool save)
{
	saveOnExit = save;
}

KeyValuePair * INIParser::getKey(const char * group, const char * name)
{
	if(group == NULL || name == NULL) return NULL;
	INIGroup * g = groups.get(group);
	if(g == NULL) return NULL;

	return g->getKey(name);
}

int INIParser::getValueInt(const char * group, const char * name, int def)
{
	if(group == NULL || name == NULL) return def;
	KeyValuePair * kvp = getKey(group, name);
	if(kvp == NULL) return def;
	return kvp->getValInt(def);
}

unsigned int INIParser::getValueUXInt(const char * group, const char * name, unsigned int def)
{
	if(group == NULL || name == NULL) return def;
	KeyValuePair * kvp = getKey(group, name);
	if(kvp == NULL) return def;
	return kvp->getValUXInt(def);
}

float INIParser::getValueFloat(const char * group, const char * name, float def)
{
	if(group == NULL || name == NULL) return def;
	KeyValuePair * kvp = getKey(group, name);
	if(kvp == NULL) return def;
	return kvp->getValFloat(def);
}

const char * INIParser::getValue(const char * group, const char * name)
{
	if(group == NULL || name == NULL) return NULL;
	KeyValuePair * kvp = getKey(group, name);
	if(kvp == NULL) return NULL;
	return kvp->value;
}

bool INIParser::notNull(const char * group, const char * name)
{
	if(group == NULL || name == NULL) return false;
	KeyValuePair * kvp = getKey(group, name);
	if(kvp == NULL) return false;
	return kvp->value[0] != '\0';
}


const char * INIParser::getValueDef(const char * group, const char * name, const char * def)
{
	if(group == NULL || name == NULL) return def;
	KeyValuePair * kvp = getKey(group, name);
	if(kvp == NULL) return def;
	return kvp->value;
}

INIGroup * INIParser::getGroup(const char * name)
{
	return groups.get(name);
}

KeyValuePair * INIParser::touchKey(const char * group, const char * name)
{
	if(group == NULL || name == NULL) return NULL;
	INIGroup * g = groups.get(group);
	if(g == NULL)
	{
		g = new INIGroup();
		g->setTitle(group);
		groups.put(group, g);
	}

	return g->touchKey(name);
}

void INIParser::setValue(const char * group, const char * name, const char * value)
{
	KeyValuePair * kvp = touchKey(group, name);
	kvp->setValue(value);
}

void INIParser::setValueInt(const char * group, const char * name, int value)
{
	KeyValuePair * kvp = touchKey(group, name);
	kvp->setValInt(value);
}

void INIParser::setValueFloat(const char * group, const char * name, float value)
{
	KeyValuePair * kvp = touchKey(group, name);
	kvp->setValFloat(value);
}

bool INIParser::loadFromBuffer(ByteStream * bs)
{
	char line[1024];
	INIGroup * g = NULL;

	groups.clear(true);

	while(bs->pos < bs->length)
	{
		bs->readLINE(line, 1024);

		trimSpaces(line);
		//std::cout << "LINE : \"" << line << "\"\n";
		//RLog("INILine : \"%s\"", line);
		int len = strlen(line);
		if(len < 3) continue;
		if(line[0] == '#') continue;

		if(line[0] == '[')
		{
			for(int i = 0; i < len - 1; ++i)
				line[i] = line[i + 1];
			len -= 2;
			line[len] = '\0';

			if(g != NULL)
				groups.put(g->getTitle(), g);

			g = new INIGroup();
			g->setTitle(line);
		}
		else if(g != NULL)
		{
			if(strchr(line, '=') == NULL) continue;
			KeyValuePair * kvp = new KeyValuePair(" ", " ");
			kvp->parseLine(line);
			g->putKVP(kvp);
		}
	}

	if(g != NULL)
		groups.put(g->getTitle(), g);

	return true;
}

bool INIParser::loadFromFile(const char * fileName)
{
	if(fileName == NULL) return false;
	if(strlen(fileName) == 0) return false;

	int size = R4::FSys->getFileSize(fileName);
	if(size == 0)
	{
		RError("File %s not found", fileName);
		return false;
	}

	ByteStream * bs = R4::FSys->readFile(fileName);
	if(bs == NULL) { RError("Loading \"%s\" failed.", fileName);  return false; }

	loadFromBuffer(bs);
	delete bs;
	strcpy(lastFileName, fileName);

	return true;
}

void INIParser::setIfNotSet(const char * group, const char * name, const char * value)
{
	if(getValue(group, name) == NULL)
		setValue(group, name, value);
}

INIParser::INIParser(const char * fileName)
{
	lastFileName[0] = '\0';
	saveOnExit = false;
	groups.setDeleteOnExit(true);
	//groups.setDeleteOnRemove(true);
	loadFromFile(fileName);
}

INIParser::INIParser(ByteStream * buffer)
{
	lastFileName[0] = '\0';
	saveOnExit = false;
	groups.setDeleteOnExit(true);
	//groups.setDeleteOnRemove(true);
	loadFromBuffer(buffer);
}
